package com.jt.common.Collection;

/**
 * 自定一个阻塞式队列容器
 * 数据存储结构：有界数组
 * 存取规则：FIFO((放数据永远放在size位置，永远从下标为0的位置取数据))
 * 放数据规则：永远放在size位置，满了则阻塞
 * 取数据规则：永远从下标为0的位置取数据，空了则阻塞
 */
class BlockQueueContainer<T>{
    /**存储数据的容器*/
    private Object[] array;
    /**记录有效元素个数*/
    private int size;
    public BlockQueueContainer(int cap){
        array=new Object[cap];
    }
    /**用于向容器放数据,假如容器满了要阻塞
     * 1)阻塞方法用wait()，wait()方法执行后会释放锁
     * 2)唤醒方法用notifyAll()
     * 说明：wait()和notifyAll()方法必须应用在同步代码块或同步方法中，
     * 由对象锁(同步锁)来调用
     * */
    public synchronized void put(T t){
        //1.判定容器是否已满，满了则阻塞当前线程
        while(size==array.length)
            try{this.wait();}catch (Exception e){}
        //2.在size位置放数据
        array[size]=t;
        //3.有效元素个数加1(也是确定下一个元素的位置)
        size++;
        //4.通知取数据的线程(唤醒处于wait状态的线程)可以取数据了
        this.notifyAll();
    }
    /**用于从容器去数据*/
    public synchronized T take(){
        //1.假如容器已空，则让取数据的线程阻塞
        while(size==0)
            try{this.wait();}catch (Exception e){}
        //2.从下标0位置开始取
        Object data=array[0];
        //3.移动元素(将下标0后面的元素依次前移)
        System.arraycopy(array, 1, array, 0, size-1);
        //4.有效元素个数减一
        size--;
        //5.通过放数据的线程(唤醒放数据时处于阻塞的线程)可以继续放了
        this.notifyAll();
        return (T)data;
    }
}

public class MyBlockQueueTests {
    public static void main(String[] args) {
        BlockQueueContainer<Integer> bqc=
                new BlockQueueContainer<>(2);
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                int i=1;
                while(true){
                    bqc.put(i++);
                    try{Thread.sleep(1000);}
                    catch (Exception e){}
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    Object data=bqc.take();
                    System.out.println(data);
                    try{Thread.sleep(1000);}
                    catch (Exception e){}
                }
            }
        });
        t1.start();
        t2.start();

    }
}
